// This is the main DLL file.

#include "stdafx.h"

#include "HidDevice.h"
#include "HidAsyncState.h"

using namespace HidLib;
using namespace System;
using namespace System::Runtime::InteropServices;

HidDevice::HidDevice()
{
	_DeviceEventMonitor = gcnew HidDeviceEventMonitor(this);
	_DeviceReadMode = DeviceMode::NonOverlapped;
	_DeviceWriteMode = DeviceMode::NonOverlapped;
}

HidDevice::HidDevice(String^ DevicePath1, String ^Description)
{
	_DevicePath = DevicePath1;
	_Description = Description;
	_DeviceReadMode = DeviceMode::NonOverlapped;
	_DeviceWriteMode = DeviceMode::NonOverlapped;
	_DeviceEventMonitor = gcnew HidDeviceEventMonitor(this);

	try
	{
		HANDLE HidHandle = OpenDeviceIO(_DevicePath, NULL);

		_DeviceAttributes = GetDeviceAttributes(HidHandle);
		_DeviceCapabilities = GetDeviceCapabilities(HidHandle);

		CloseDeviceIO(HidHandle);
	}
	catch(Exception^ Ex)
	{
	}
}

HANDLE HidDevice::OpenDeviceIO(String^ DevicePath1, DWORD DeviceAccess)
{
	return OpenDeviceIO(DevicePath1, DeviceMode::NonOverlapped, DeviceAccess);
}

HANDLE HidDevice::OpenDeviceIO(String^ DevicePath1, HidDevice::DeviceMode Mode, DWORD DeviceAccess)
{
	SECURITY_ATTRIBUTES Security;
	HANDLE FileHandle;
	DWORD Flags = 0;

	Security.bInheritHandle = TRUE;
	Security.lpSecurityDescriptor = NULL;
	Security.nLength = sizeof(SECURITY_ATTRIBUTES);
	
	if (Mode == HidDevice::DeviceMode::Overlapped)
		Flags = FILE_FLAG_OVERLAPPED;

	IntPtr PtrDevicePath = Marshal::StringToCoTaskMemUni(DevicePath1);
	FileHandle = CreateFile((LPCWSTR)PtrDevicePath.ToPointer(), 
							DeviceAccess, 
							FILE_SHARE_READ|FILE_SHARE_WRITE, 
							&Security,
							OPEN_EXISTING, 
							Flags,
							NULL);
	Marshal::FreeCoTaskMem(PtrDevicePath);

	return FileHandle;
}

void HidDevice::CloseDeviceIO(HANDLE HidHandle)
{
	CancelIo(HidHandle);
	CloseHandle(HidHandle);
}

HidDeviceAttributes^ HidDevice::GetDeviceAttributes(HANDLE HidHandle)
{
	HIDD_ATTRIBUTES DeviceAttributes;

	DeviceAttributes.Size = sizeof(DeviceAttributes);
	HidD_GetAttributes(HidHandle, &DeviceAttributes);

	return gcnew HidDeviceAttributes(DeviceAttributes);
}

HidDeviceCapabilities^ HidDevice::GetDeviceCapabilities(HANDLE HidHandle)
{
	HIDP_CAPS Caps;
	PHIDP_PREPARSED_DATA PreparsedData;

	if(HidD_GetPreparsedData(HidHandle, &PreparsedData) == TRUE)
	{
		HidP_GetCaps(PreparsedData, &Caps);
	}
	HidD_FreePreparsedData(PreparsedData);

	return gcnew HidDeviceCapabilities(Caps);
}

void HidDevice::Open()
{
	Open(_DeviceReadMode, _DeviceWriteMode);
}

void HidDevice::Open(HidDevice::DeviceMode _ReadMode, HidDevice::DeviceMode _WriteMode)
{
	if(IsOpen)
		return;

	_DeviceReadMode = _ReadMode;
	_DeviceWriteMode = _WriteMode;

	try
	{
		_HidReadHandle = OpenDeviceIO(_DevicePath, _ReadMode, GENERIC_READ);
		_HidWriteHandle = OpenDeviceIO(_DevicePath, _WriteMode, GENERIC_WRITE);
		_DeviceEventMonitor->Enabled = true;
	}
	catch(Exception^ Ex)
	{
		_DeviceOpen = false;
#if HIDLOGGING
				Trace.WriteLine("HidDevice Open");
				Trace.WriteLine(Ex.Message + Ex.StackTrace);
#endif
	}

	_DeviceOpen = _HidReadHandle != INVALID_HANDLE_VALUE && _HidWriteHandle != INVALID_HANDLE_VALUE;
}

void HidDevice::Read(ReadCallback^ Callback)
{
	ReadDelegate^ ReadDel = gcnew ReadDelegate(this, &HidDevice::Read);
	HidAsyncState^ AsyncState = gcnew HidAsyncState(ReadDel, Callback);

	ReadDel->BeginInvoke(gcnew AsyncCallback(&HidDevice::EndRead), AsyncState);
}

HidDeviceData^ HidDevice::Read()
{
	return Read(0);
}

HidDeviceData^ HidDevice::Read(UInt32 Timeout)
{
	if(IsConnected)
	{
		if(!IsOpen)
			Open();

		try
		{
			return ReadData(Timeout);
		}
		catch(Exception^ Ex)
		{
			return gcnew HidDeviceData(HidDeviceData::ReadStatus::ReadError);
		}
	}
	return gcnew HidDeviceData(HidDeviceData::ReadStatus::NotConnected);
}

HidDeviceData^ HidDevice::ReadData(UInt32 Timeout)
{
	array<Byte>^ Buffer;
	HidDeviceData::ReadStatus Status = HidDeviceData::ReadStatus::NoDataRead;

	if(_DeviceCapabilities->InputReportByteLength > 0)
	{
		DWORD BytesRead = 0;
		DWORD Result;
		
		if(_DeviceReadMode == DeviceMode::Overlapped)
		{
			DWORD OverlappedTimeout = INFINITE;

			if (Timeout > 0)
				OverlappedTimeout = Timeout;

			try
			{
				BYTE* NatBuffer = new BYTE[_DeviceCapabilities->InputReportByteLength];

				OVERLAPPED Overlap;
				Overlap.Internal = 0;
				Overlap.InternalHigh = 0;
				Overlap.Offset = 0;
				Overlap.OffsetHigh = 0;
				
				SECURITY_ATTRIBUTES Security;
						
				Security.bInheritHandle = TRUE;
				Security.lpSecurityDescriptor = NULL;
				Security.nLength = sizeof(Security);

				HANDLE _ReadEvent = CreateEvent(&Security, FALSE, FALSE, L"");
				Overlap.hEvent = _ReadEvent;

				BOOL BRes = ReadFile(_HidReadHandle, NatBuffer, (DWORD)_DeviceCapabilities->InputReportByteLength, &BytesRead, &Overlap);
				WaitForSingleObjectEx(_ReadEvent, OverlappedTimeout, true);
				if (GetOverlappedResult(_HidReadHandle,&Overlap,&BytesRead, TRUE) == 0)
				{
					DWORD err = GetLastError();
				}
				CloseHandle(_ReadEvent);
				
				switch(Result)
				{
					case WAIT_OBJECT_0:
						Status = HidDeviceData::ReadStatus::Success;
						Buffer = gcnew array<Byte>(BytesRead);
						for(int i = 0; i < BytesRead; i++)
						{
							Buffer[i] = NatBuffer[i];
						}
						break;
					case WAIT_TIMEOUT:
						Status = HidDeviceData::ReadStatus::WaitTimedOut;
						Buffer = gcnew array<Byte>(0);
						break;
					case WAIT_FAILED:
						Status = HidDeviceData::ReadStatus::WaitFail;
						Buffer = gcnew array<Byte>(0);
						break;
					default:
						Status = HidDeviceData::ReadStatus::NoDataRead;
						Buffer = gcnew array<Byte>(0);
						break;
				}
				free(NatBuffer);
			}
			catch(Exception^ Ex)
			{
				Status = HidDeviceData::ReadStatus::ReadError;
			}
		}
		else
		{
			try
			{
				BYTE* NatBuffer = new BYTE[_DeviceCapabilities->InputReportByteLength];
				ReadFile(_HidReadHandle, NatBuffer, (DWORD)_DeviceCapabilities->InputReportByteLength, &BytesRead, NULL);
				Status = HidDeviceData::ReadStatus::Success;
				Buffer = gcnew array<Byte>(BytesRead);
				for(int i = 0; i < BytesRead; i++)
				{
					Buffer[i] = NatBuffer[i];
				}
				free(NatBuffer);
			}
			catch(Exception^ Ex)
			{
				Status = HidDeviceData::ReadStatus::ReadError;
			}
		}
	}
	return gcnew HidDeviceData(Buffer, Status);
}

void HidDevice::EndRead(IAsyncResult^ Ar)
{
	HidAsyncState^ AsyncState = (HidAsyncState^)Ar->AsyncState;
	ReadDelegate^ CallerDel = (ReadDelegate^)AsyncState->CallerDelegate;
	ReadCallback^ CallbackDel = (ReadCallback^)AsyncState->CallbackDelegate;
	HidDeviceData^ Data = CallerDel->EndInvoke(Ar);

	if(CallbackDel)
		CallbackDel(Data);
}

array<Byte>^ HidDevice::CreateOutputBuffer()
{
	return CreateBuffer(_DeviceCapabilities->OutputReportByteLength);
}

array<Byte>^ HidDevice::CreateInputBuffer()
{
	return CreateBuffer(_DeviceCapabilities->InputReportByteLength);
}

array<Byte>^ HidDevice::CreateBuffer(int BufferLength)
{
	return gcnew array<Byte>(BufferLength);
}

void HidDevice::Close()
{
	Close(true);
}

void HidDevice::Close(bool DisableDeviceMonitor)
{
	if(DisableDeviceMonitor)
		_DeviceEventMonitor->Enabled = false;

	if(!IsOpen)
		return;

	try
	{
		CloseDeviceIO(_HidReadHandle);
		CloseDeviceIO(_HidWriteHandle);
	}
	catch(Exception^ Ex)
	{
#if HIDLOGGING
		Trace.WriteLine("HidDevice Open");
		Trace.WriteLine(Ex.Message + Ex.StackTrace);
#endif
	}
	finally
	{
		_DeviceOpen = false;
	}
}

HidReport^ HidDevice::ReadReport()
{
	return ReadReport(0);
}

HidReport^ HidDevice::ReadReport(UInt32 Timeout)
{
	return gcnew HidReport(_DeviceCapabilities->InputReportByteLength, Read(Timeout));
}

void HidDevice::ReadReport(ReadReportCallback^ Callback)
{
	ReadReportDelegate^ ReadReportDel = gcnew ReadReportDelegate(this, &HidDevice::ReadReport);
	HidAsyncState^ AsyncState = gcnew HidAsyncState(ReadReportDel, Callback);
	ReadReportDel->BeginInvoke(gcnew AsyncCallback(&HidDevice::EndReadReport), AsyncState);
}

void HidDevice::EndReadReport(IAsyncResult^ Ar)
{
	HidAsyncState^ AsyncState = (HidAsyncState^)Ar->AsyncState;
	ReadReportDelegate^ CallerDel = (ReadReportDelegate^)AsyncState->CallerDelegate;
	ReadReportCallback^ CallbackDel = (ReadReportCallback^)AsyncState->CallbackDelegate;
	HidReport^ Report = CallerDel->EndInvoke(Ar);

	if(CallbackDel)
		CallbackDel(Report);
}

void HidDevice::Write(array<Byte>^ Data, WriteCallback^ Callback)
{
	WriteDelegate^ WriteDel = gcnew WriteDelegate(this, &HidDevice::Write);
	HidAsyncState^ AsyncState = gcnew HidAsyncState(WriteDel, Callback);

	WriteDel->BeginInvoke(Data, gcnew AsyncCallback(&HidDevice::EndWrite), AsyncState);
}

bool HidDevice::Write(array<Byte>^ Data)
{
	return Write(Data, 0);
}

bool HidDevice::Write(array<Byte>^ Data, UInt32 Timeout)
{
	if(IsConnected)
	{
		if(!IsOpen)
			Open();
		try
		{
			return WriteData(Data, Timeout);
		}
		catch(Exception^ Ex)
		{
		}
	}
	return false;
}

void HidDevice::WriteReport(HidReport^ Report, WriteCallback^ Callback)
{
	WriteReportDelegate^ WriteReportDel = gcnew WriteReportDelegate(this, &HidDevice::WriteReport);
	HidAsyncState^ AsyncState = gcnew HidAsyncState(WriteReportDel, Callback);

	WriteReportDel->BeginInvoke(Report, gcnew AsyncCallback(&HidDevice::EndWriteReport), AsyncState);
}

bool HidDevice::WriteReport(HidReport^ Report)
{
	return WriteReport(Report, 0);
}

bool HidDevice::WriteReport(HidReport^ Report, UInt32 Timeout)
{
	return Write(Report->GetBytes(), Timeout);
}

void HidDevice::EndWrite(IAsyncResult^ Ar)
{
	HidAsyncState^ AsyncState = (HidAsyncState^)Ar->AsyncState;
	WriteDelegate^ CallerDel = (WriteDelegate^)AsyncState->CallerDelegate;
	WriteCallback^ CallbackDel = (WriteCallback^)AsyncState->CallbackDelegate;

	bool Result = CallerDel->EndInvoke(Ar);
	if(CallbackDel)
		CallbackDel(Result);
}

void HidDevice::EndWriteReport(IAsyncResult^ Ar)
{
	HidAsyncState^ AsyncState = (HidAsyncState^)Ar->AsyncState;
	WriteReportDelegate^ CallerDel = (WriteReportDelegate^)AsyncState->CallerDelegate;
	WriteCallback^ CallbackDel = (WriteCallback^)AsyncState->CallbackDelegate;

	bool Result = CallerDel->EndInvoke(Ar);
	if(CallbackDel)
		CallbackDel(Result);
}

HidReport^ HidDevice::CreateReport()
{
	return gcnew HidReport(_DeviceCapabilities->OutputReportByteLength);
}

bool HidDevice::WriteData(array<Byte>^ Data, UInt32 Timeout)
{
	if(_DeviceCapabilities->OutputReportByteLength > 0)
	{
		BYTE* Buffer = new BYTE[_DeviceCapabilities->OutputReportByteLength];
		DWORD BytesWritten = 0;
		DWORD Result;

		int DataLen = Math::Min((int)Data->Length, (int)_DeviceCapabilities->OutputReportByteLength);
		for(int i = 0; i < DataLen; i++)
		{
			Buffer[i] = Data[i];
		}

		if(_DeviceWriteMode == DeviceMode::Overlapped)
		{
			DWORD OverlapTimeout = INFINITE;

			if (Timeout > 0)
				OverlapTimeout = Timeout;

			OVERLAPPED Overlap;
			Overlap.Internal = 0;
			Overlap.InternalHigh = 0;
			Overlap.Offset = 0;
			Overlap.OffsetHigh = 0;
			SECURITY_ATTRIBUTES Security;
						
			Security.bInheritHandle = TRUE;
			Security.lpSecurityDescriptor = NULL;
			Security.nLength = sizeof(Security);

			HANDLE _WriteEvent = CreateEvent(&Security, FALSE, FALSE, L"");
			Overlap.hEvent = _WriteEvent;
			try
			{
				WriteFile(_HidWriteHandle, Buffer, _DeviceCapabilities->OutputReportByteLength, &BytesWritten, &Overlap);
			}
			catch(Exception^ Ex)
			{
#if HIDLOGGING
				Trace.WriteLine("HidDevice Open");
				Trace.WriteLine(Ex.Message + Ex.StackTrace);
#endif
				return false;
			}

			WaitForSingleObjectEx(_WriteEvent, OverlapTimeout, true);
			if (GetOverlappedResult(_HidWriteHandle,&Overlap,&BytesWritten, TRUE) == 0)
			{
				DWORD err = GetLastError();
			}
			ResetEvent(_WriteEvent);

			switch(Result)
			{
				case WAIT_OBJECT_0:
					return true;
				case WAIT_TIMEOUT:
					return false;
				case WAIT_FAILED:
					return false;
				default:
					return false;
			}
		}
		try
		{
			return WriteFile(_HidWriteHandle, Buffer, _DeviceCapabilities->OutputReportByteLength, &BytesWritten, NULL) == FALSE ? false : true;
		}
		catch(Exception^ Ex)
		{
			return false;
		}
	}
	return false;
}